<?php
abstract class Pageley_Serializer_Abstract implements Pageley_Serializer {

    public function serialize($entity) {
        $type = $entity->getType();
        $serialized = new Pageley_Util_ArrayMap();
        foreach($type->getAttributes() as $propertyName => $attribute) {
            if(!$attribute->isMultiple()) {
                $value = $entity->get($propertyName);
                $serialized->putAll($this->serializeProperty($propertyName, $value, $attribute));
            }
        }
        return $serialized;
    }

    public function deserialize($values, $type, $prefix = "") {
        $entity = $type->newInstance();
        return $this->deserializeInternal($values, $type, $entity, $prefix);
    }

    public function deserializeInto($values, $entity, $prefix = "") {
        $type = $entity->getType();
        return $this->deserializeInternal($values, $type, $entity, $prefix);
    }

    private function deserializeInternal($values, $type, $entity, $prefix) {
    	$properties = new Pageley_Util_ArrayMap();
        $errors = new Pageley_Util_ArrayMap();
    	foreach($type->getAttributes() as $propertyName => $attribute) {
            try {
                $this->deserializeProperty($propertyName, $values, $attribute, $properties, $errors, $prefix);
            } catch(Pageley_Serializer_Exception $e) {
                Pageley_Log::debug($this, "Validation of property '" . $propertyName . "' failed: " . $e->getMessage());
                $errors->put($propertyName, new Pageley_I18n_Message("error.validation.invalid", array("{field." . $propertyName . "}")));
            }
    	}
    	foreach($properties as $propertyName => $value) {
    		try {
                $entity->set($propertyName, $value, $this->shouldValidate());
    		} catch(Pageley_Domain_Validator_Exception $e) {
    			if(Pageley_Log::isDebugEnabled($this)) {
    				Pageley_Log::debug($this, "Validation of property '" . $propertyName . "' failed: " . $e->getErrors());
    			}
    			$errors->putAll($e->getErrors());
    		}
        }
        if($errors->size() > 0) {
        	throw new Pageley_Domain_Validator_Exception($errors);
        }
        return $entity;
    }

    private function serializeProperty($propertyName, $value, $attribute) {
        $serialized = new Pageley_Util_ArrayMap();
        if($attribute->isComposite()) {
        	foreach($attribute->getAttributes() as $subpropertyName => $subattribute) {
        		$subvalue = $value != null ? $value->get($subpropertyName) : null;
                $serialized->putAll($this->serializeProperty($propertyName . "." . $subpropertyName,
                    $subvalue, $subattribute));
            }
        } else {
            $converter = $attribute->getConverter();
            if($converter !== null) {
	            $converted = $converter->convertIntoFormat($value);
	            $serializedName = $this->serializeName($propertyName, $attribute);
	            $serialized->put($serializedName, $converted);
            }
        }
        return $serialized;
    }

    private function deserializeProperty($propertyName, $values, $attribute, &$properties, $errors, $prefix) {
    	if($attribute->isComposite()) {
            $subproperties = new Pageley_Util_ArrayMap();
            foreach($attribute->getAttributes() as $subpropertyName => $subattribute) {
                try {
                    $this->deserializeProperty($subpropertyName,
                        $values, $subattribute, $subproperties, $errors, $prefix . $propertyName . ".");
                } catch(Pageley_Serializer_Exception $e) {
                    Pageley_Log::debug($this, "Validation of property '" . $subpropertyName . "' failed: " . $e->getMessage());
                    $errors->put($subpropertyName, new Pageley_I18n_Message("error.validation.invalid", array("{field." . $subpropertyName . "}")));
                }
                
            }
            if($subproperties->size() > 0) {
            	$properties->put($propertyName, $attribute->newInstance($subproperties));
            }
        } else {
            $converter = $attribute->getConverter();
            if($converter !== null) {
	            $serializedName = $this->serializeName($prefix . $propertyName, $attribute);
	            if($this->containsKey($values, $serializedName) || array_key_exists(Pageley_Util_String::replace($serializedName, ".", "_"), $_FILES)) {
                    $value = $this->convertFromFormat($values, $converter, $serializedName);
                    if($value !== null && $attribute->shouldEncrypt()) {
                        $value = $this->encrypt($value);
                    }
	            	$properties->put($propertyName, $value);
	            }
            }
        }
    }
    
    protected function convertFromFormat($values, $converter, $serializedName) {
        $value = $values->get($serializedName);
        return $converter->convertFromFormat($value);
    }

    public abstract function shouldValidate();
    
    public abstract function encrypt($value);

    protected abstract function serializeName($propertyName, $attribute);

    protected abstract function containsKey($values, $serializedName);
}